//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/backend/llvm/backend.h"
#include "sagittarius/io/io.h"
#include <stdio.h>
#include <iostream>

namespace Sagittarius {
    
    /// *** CodeGen_Network ***
    
    llvm::Value* CodeGen_Network::emit_exp_node(compiledSrc* c, sg_fctx_val* z, compiledNode* x) {
        if(x == NULL)
            return llvm::ConstantExpr::getBitCast(getPValNull(), pnode_val);
        std::vector<llvm::Value*> args;
        
        // Child 1
        args.push_back(emit_exp_node(c,z,x->a));
        // Child 2
        args.push_back(emit_exp_node(c,z,x->b));
        // type
        args.push_back(getUInt64Const(x->ptype));
        // name
        if(x->ptype == PAST_NODE_TYPE_SYMBOL)
            args.push_back(getConstNameValPtr(sg_cast_as_name_val(x->val)));
        else
            args.push_back(getConstNameValPtr(NULL));
            
        // object
        if(x->ptype == PAST_NODE_TYPE_SYMBOL && sg_cast_as_name_val(x->val)->sym_tp != SG_SYMTP_RATE_LAW_CONST && sg_cast_as_name_val(x->val)->sym_tp != SG_SYMTP_SPECIES)
            args.push_back(emit_node(c, z, x));
        else
            args.push_back(getPValNull());
        
        check_farg_types(f_create_node, args);
        
        return builder->CreateCall(f_create_node, args);
    }
    
    void CodeGen_Network::emit_rxn_rule(compiledSrc* c, sg_fctx_val* z, compiledNode* x, llvm::Value* net) {
        AT(x->ptype == PAST_NODE_TYPE_RXN_RULE, "Unexpected type");
        std::vector<llvm::Value*> args;
        
        // rxn
        args.push_back(emit_exp_node(c,z,x->a));
        // rate
        args.push_back(emit_exp_node(c,z,x->b));
        
        llvm::Value* rule = builder->CreateCall(f_create_reaction, args);
        
        // add to network
        args.clear();
        args.push_back(net);
        args.push_back(rule);
        builder->CreateCall(f_network_add_rule, args);
    }
    
    void CodeGen_Network::emit_rxn_rules(compiledSrc* c, sg_fctx_val* z, compiledNode* x, llvm::Value* net) {
        AT(x->ptype == PAST_NODE_TYPE_RXN_RULE_LINK, "Unexpected type");
        
        emit_rxn_rule(c,z,x->a,net);
        
        if(x->b) {
            AT(x->b->ptype == PAST_NODE_TYPE_RXN_RULE_LINK, "Unexpected type");
            emit_rxn_rules(c, z, x->b, net);
        }
    }
    
    llvm::Value* CodeGen_Network::emit_node(compiledSrc* c, sg_fctx_val* z, compiledNode* x) {
        switch(x->ptype) {
            case PAST_NODE_TYPE_NETWORK: {
                SG_WRITE(2, "Emit network\n");
                
                llvm::Value* net = builder->CreateCall(f_create_network);
                
                emit_rxn_rules(c,z,x->a,net);
                
                return builder->CreateBitCast(net, pval);
            }
            default:
                return CodeGen_Toplevel::emit_node(c, z, x);
        }
    }
    
    void CodeGen_Network::createTypes() {
        CodeGen_NetworkSuper::createTypes();
        
        // -- object header --
        obj_header = getLLVMTypes()->make_obj_header();
        
        std::vector<llvm::Type*> obj_fields;
        
        // -- sg_node_val adaptation --
        obj_fields = obj_header;
        // Child 1
        obj_fields.push_back(pval);
        // Child 2
        obj_fields.push_back(pval);
        // name-val
        obj_fields.push_back(pname_val_tp);
        // type
        obj_fields.push_back(i64_tp);
        // object
        obj_fields.push_back(pval);
        // symbol
        obj_fields.push_back(symcode_tp);
        node_val = llvm::StructType::get(llvm::getGlobalContext(), obj_fields);
        pnode_val = llvm::PointerType::get(node_val,0);
        
        // -- sg_reaction_rule_val adaptation --
        obj_fields = obj_header;
        // reaction
        obj_fields.push_back(pnode_val);
        // rate law
        obj_fields.push_back(pnode_val);
        rxn_rule_val = llvm::StructType::get(llvm::getGlobalContext(), obj_fields);
        prxn_rule_val = llvm::PointerType::get(rxn_rule_val,0);
        
        // -- sg_network_val adaptation --
        obj_fields = obj_header;
        // reaction rules
        obj_fields.push_back(plistval_tp);
        network_val = llvm::StructType::get(llvm::getGlobalContext(), obj_fields);
        pnetwork_val = llvm::PointerType::get(network_val,0);
    }
    
    void CodeGen_Network::init_internals() {
        CodeGen_NetworkSuper::init_internals();
        
        std::vector<llvm::Type*> args;
        
        // f_create_network
        args.clear();
        f_create_network_tp = llvm::FunctionType::get(pnetwork_val, args, false);
        f_create_network = (llvm::Function*)mod->getOrInsertFunction("__f_create_network", f_create_network_tp);
        
        // f_network_add_rule
        args.clear();
        args.push_back(pnetwork_val);
        args.push_back(prxn_rule_val);
        AN(pnetwork_val);
        AN(prxn_rule_val);
        AN(void_tp);
        f_network_add_rule_tp = llvm::FunctionType::get(void_tp, args, false);
        f_network_add_rule = (llvm::Function*)mod->getOrInsertFunction("__f_network_add_rule", f_network_add_rule_tp);
        
        // f_create_reaction
        args.clear();
        args.push_back(pnode_val);
        args.push_back(pnode_val);
        f_create_reaction_tp = llvm::FunctionType::get(prxn_rule_val, args, false);
        f_create_reaction = (llvm::Function*)mod->getOrInsertFunction("__f_create_reaction", f_create_reaction_tp);
        
        // f_create_node
        args.clear();
        args.push_back(pnode_val);
        args.push_back(pnode_val);
        args.push_back(i64_tp);
        args.push_back(pname_val_tp);
        args.push_back(pval);
        f_create_node_tp = llvm::FunctionType::get(pnode_val, args, false);
        f_create_node = (llvm::Function*)mod->getOrInsertFunction("__f_create_node", f_create_node_tp);
        
    }
    
    void CodeGen_Network::setup_mappings(llvm::ExecutionEngine* EE) {
        // super
        CodeGen_NetworkSuper::setup_mappings(EE);
        
        // global mappings
        EE->addGlobalMapping(f_create_network, (void*)(&sg_create_network));
        EE->addGlobalMapping(f_network_add_rule, (void*)(&sg_network_add_rule));
        EE->addGlobalMapping(f_create_reaction, (void*)(&sg_create_reaction));
        EE->addGlobalMapping(f_create_node, (void*)(&sg_create_node));
    }
    
}
